package com.tjbklx1.redis.chap05;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.lang3.tuple.Pair;

import com.tjbklx1.redis.JedisUtil;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;

/**
 * 计数器案例
 * @author tjbklx1
 * @date 2018-12-6
 */
public class App53_Counter {
	
	/**
	 * 日志的精度
	 */
	public static final int[] PRECISION = new int[] { 1, 5, 60, 300, 3600, 18000, 86400 };
	
	
	public static void main(String[] args) throws Exception {
		Jedis conn = JedisUtil.getJedisWithDB();
		new App53_Counter().testCounters(conn);
	}

	public void testCounters(Jedis conn) throws InterruptedException {
		System.out.println("\n----- testCounters -----");
		System.out.println("Let's update some counters for now and a little in the future");
		long now = System.currentTimeMillis() / 1000;  // 当前的秒
		for (int i = 0; i < 10; i++) {
			int count = (int) (Math.random() * 5) + 1;
			updateCounter(conn, "test", count, now + i);  //更新计数器
		}

		//查询1分钟计数器
		List<Pair<Integer, Integer>> counter = getCounter(conn, "test", 1);
		System.out.println("We have some per-second counters: " + counter.size());
		System.out.println("These counters include:");
		for (Pair<Integer, Integer> count : counter) {
			System.out.println("  " + count);
		}
		assert counter.size() >= 10;

		//查询5分钟计数器
		counter = getCounter(conn, "test", 5);
		System.out.println("We have some per-5-second counters: " + counter.size());
		System.out.println("These counters include:");
		for (Pair<Integer, Integer> count : counter) {
			System.out.println("  " + count);
		}
		assert counter.size() >= 2;
		System.out.println();

		//清理计数器
		System.out.println("Let's clean out some counters by setting our sample count to 0");
		CleanCountersThread thread = new CleanCountersThread(0, 2 * 86400000);
		thread.start();
		Thread.sleep(1000);
		thread.quit();
		thread.interrupt();
		
		// 查询86400计数器
		counter = getCounter(conn, "test", 86400);
		System.out.println("Did we clean out all of the counters? " + (counter.size() == 0));
		assert counter.size() == 0;
	}

	/**
	 * 更新计数器
	 * @param conn
	 * @param name name
	 * @param count 数值
	 */
//	public void updateCounter(Jedis conn, String name, int count) {
//		updateCounter(conn, name, count, System.currentTimeMillis() / 1000);
//	}

	/**
	 * 更新计数器
	 * @param conn
	 * @param name
	 * @param count 数值
	 * @param now 时间 单位秒
	 */
	public void updateCounter(Jedis conn, String name, int count, long now) {
		Transaction trans = conn.multi();
		for (int prec : PRECISION) {
			long pnow = (now / prec) * prec; //时间 ,时间/精度*精度 ,整数的精度
			String hash = String.valueOf(prec) + ':' + name; // 时间精度:name
			trans.zadd("known:", 0, hash); // known  ????
			trans.hincrBy("count:" + hash, String.valueOf(pnow), count); // hash 计数器 ,[count:时间精度:name] 时间,数值
		}
		trans.exec();
	}

	/**
	 * 根据时间精度查询计数器
	 * @param conn
	 * @param name name
	 * @param precision 时间精度
	 * @return
	 */
	public List<Pair<Integer, Integer>> getCounter(Jedis conn, String name, int precision) {
		String hash = String.valueOf(precision) + ':' + name; 
		Map<String, String> data = conn.hgetAll("count:" + hash);//组织KEY [count:时间精度:name],查询HASH
		ArrayList<Pair<Integer, Integer>> results = new ArrayList<Pair<Integer, Integer>>();
		for (Map.Entry<String, String> entry : data.entrySet()) {
			Pair<Integer, Integer> t = Pair.of(Integer.parseInt(entry.getKey()), Integer.parseInt(entry.getValue()));
			results.add(t);
		}
		Collections.sort(results);// 排序
		return results;// 返回排序后的集合
	}

	/**
	 * 清理计数器
	 * @author tjbklx1
	 * @date 2018-12-6
	 */
	public class CleanCountersThread extends Thread {
		private Jedis conn;
		private int sampleCount = 100;
		private boolean quit;
		private long timeOffset; // used to mimic a time in the future.

		public CleanCountersThread(int sampleCount, long timeOffset) {
			this.conn = new Jedis("localhost");
			this.conn.select(15);
			this.sampleCount = sampleCount;
			this.timeOffset = timeOffset;
		}

		public void quit() {
			quit = true;
		}

		public void run() {
			int passes = 0;
			while (!quit) {
				long start = System.currentTimeMillis() + timeOffset;
				int index = 0;
				while (index < conn.zcard("known:")) {
					Set<String> hashSet = conn.zrange("known:", index, index);
					index++;
					if (hashSet.size() == 0) {
						break;
					}
					String hash = hashSet.iterator().next();
					int prec = Integer.parseInt(hash.substring(0, hash.indexOf(':')));
					int bprec = (int) Math.floor(prec / 60);
					if (bprec == 0) {
						bprec = 1;
					}
					if ((passes % bprec) != 0) {
						continue;
					}

					String hkey = "count:" + hash;
					String cutoff = String.valueOf(((System.currentTimeMillis() + timeOffset) / 1000) - sampleCount
							* prec);
					ArrayList<String> samples = new ArrayList<String>(conn.hkeys(hkey));
					Collections.sort(samples);
					int remove = bisectRight(samples, cutoff);

					if (remove != 0) {
						conn.hdel(hkey, samples.subList(0, remove).toArray(new String[0]));
						if (remove == samples.size()) {
							conn.watch(hkey);
							if (conn.hlen(hkey) == 0) {
								Transaction trans = conn.multi();
								trans.zrem("known:", hash);
								trans.exec();
								index--;
							} else {
								conn.unwatch();
							}
						}
					}
				}

				passes++;
				long duration = Math.min((System.currentTimeMillis() + timeOffset) - start + 1000, 60000);
				try {
					sleep(Math.max(60000 - duration, 1000));
				} catch (InterruptedException ie) {
					Thread.currentThread().interrupt();
				}
			}
		}

		// mimic python's bisect.bisect_right
		public int bisectRight(List<String> values, String key) {
			int index = Collections.binarySearch(values, key);  // key 是否在集合values中
			return index < 0 ? Math.abs(index) - 1 : index + 1;
		}
	}
}
